Skip to content

feat(compile): add pool-overrides for per-job pool configuration (#1464)#1472

Merged
jamesadevine merged 9 commits into
mainfrom
copilot/better-front-matter-plan
Jul 13, 2026
Merged

feat(compile): add pool-overrides for per-job pool configuration (#1464)#1472
jamesadevine merged 9 commits into
mainfrom
copilot/better-front-matter-plan

Conversation

Copilot AI commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Summary

The compiler previously cloned the top-level pool: onto every generated job,
forcing lightweight downstream jobs (Detection, SafeOutputs, Conclusion) onto
scarce specialized capacity. This adds pool.overrides: to right-size
individual jobs independently.

Per-job overrides are nested under pool.overrides: (a sub-key of pool:, not
a separate top-level key). Each override value uses the object pool form —
the same schema as the top-level pool:.

pool:
  name: SpecializedLinuxPool       # default for all jobs
  overrides:
    detection:
      vmImage: ubuntu-22.04        # lightweight Microsoft-hosted image
    safe-outputs:
      vmImage: ubuntu-22.04
    conclusion:
      vmImage: ubuntu-22.04

Valid override keys: setup, agent, detection, safe-outputs,
safe-outputs-reviewed, teardown, conclusion. safe-outputs-reviewed
inherits the safe-outputs override unless it has its own entry. manual-review
is always rejected (the ManualReview job is agentless and fixed to pool: server).
Unknown keys emit a compiler warning and are ignored (forward-compat).

Changes

src/compile/types.rsoverrides: HashMap<String, PoolConfig> field added
to the pool object. The legacy untagged PoolConfig enum (scalar Name(String)

  • Full) was removed and the object struct (formerly PoolConfigFull) is now the
    single PoolConfig type. The typed layer is object-form only: the legacy
    scalar shorthand pool: MyPool is still accepted as source input and rewritten
    to pool: { name: MyPool } by codemod 0002_pool_object_form before typed
    deserialization, so a bare string never reaches the type. Pool accessors
    (name/vm_image/os/demands/overrides) live on the struct and are used by
    both production and tests.

src/compile/common.rsPerJobPools struct (one resolved Pool per
canonical job) + resolve_pool_overrides_typed() which applies overrides,
inherits safe-outputs-reviewed from safe-outputs, warns on unknown keys, and
hard-errors on manual-review. resolve_pool_typed goes through the PoolConfig
accessors.

src/compile/agentic_pipeline.rsStandaloneCtx.pool: Poolpools: PerJobPools; each of the job builders reads its own field. SafeOutputsVariant
carries an is_reviewed: bool so the reviewed-variant pool selection no longer
relies on a string comparison against the job base name.

src/compile/onees_ir.rs — compile-time error if pool.overrides: is
non-empty on target: 1es; the 1ES template owns pool selection.

Constraints: Each override is validated through the same resolve_pool_typed
rules as the default pool (name: and vmImage: are mutually exclusive;
demands: requires name:). Override values accept only the object form — a bare
pool-name string is not accepted (use name:).

Docs: Full reference section added to front-matter.md (key table + trust
boundary note covering agent/setup source-tree exposure and Detection/
SafeOutputs/Conclusion SC_WRITE_TOKEN exposure); trust boundary callout added to
safe-outputs.md.

Test plan

Unit tests in common.rs cover default inheritance, per-key overrides,
safe-outputs-reviewed inheritance, manual-review rejection, invalid pool
combos, and demands composition; integration tests in agentic_pipeline.rs verify
pools flow through build_canonical_jobs to the emitted job objects; a
parse_markdown regression test confirms a legacy scalar pool: source still
compiles via the codemod. cargo test and cargo clippy --all-targets --all-features pass clean.

Copilot AI and others added 2 commits July 11, 2026 14:58
Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com>
…ed signature

Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com>
Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com>
@jamesadevine jamesadevine marked this pull request as ready for review July 11, 2026 16:16
@jamesadevine

Copy link
Copy Markdown
Collaborator

/rust-review

@github-actions

github-actions Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Rust PR Reviewer completed successfully!

@github-actions

Copy link
Copy Markdown
Contributor

🔍 Rust PR Review

Summary: Looks good — the implementation is correct and well-tested. One fragility concern and one minor nit worth noting.

Findings

⚠️ Suggestions

  • src/compile/agentic_pipeline.rs:1511 — Pool selection for SafeOutputs_Reviewed uses a string comparison against variant.base:

    let safeoutputs_pool = if variant.base == "SafeOutputs_Reviewed" {
        cfg.pools.safe_outputs_reviewed.clone()
    } else {
        cfg.pools.safe_outputs.clone()
    };

    SafeOutputsVariant is a private struct with only three constructors, so the risk is contained, but this is silently wrong if the name is ever refactored. A boolean field like is_reviewed: bool on SafeOutputsVariant — already set in the reviewed() constructor — would be more robust and self-documenting.

  • src/compile/types.rs:116 and src/compile/types.rs:1222 — Two separate static EMPTY: OnceLock<HashMap<String, PoolConfig>> statics exist: one in PoolConfig::overrides() and one in FrontMatter::pool_overrides(). This is correct but the duplication is worth consolidating (e.g., a single module-level static or a fn empty_overrides() helper) to keep the surface smaller.

✅ What Looks Good

  • Sanitization is complete: Switching PoolConfigFull from #[derive(SanitizeConfig)] to a manual SanitizeConfigTrait impl is the right call — the derive macro can't handle HashMap<String, PoolConfig>, and the manual impl correctly recurses into each override value. No sanitization gap.
  • manual-review rejection is fail-closed: The forbidden-key check runs before the unknown-key warning loop and uses bail!, so alternate spellings (manualreview, manual_review) are hard-rejected rather than silently ignored.
  • safe-outputs-reviewed inheritance is clean — resolved once from safe-outputs and only overridden when the key is explicitly present; the logic reads clearly and the unit tests cover both paths.
  • 1ES guard is a clean, early compile-time error with an actionable message.
  • Test coverage is thorough: 9 unit tests in common.rs cover the full override matrix, and the 3 integration tests in agentic_pipeline.rs verify the pools actually reach the emitted job objects.

Generated by Rust PR Reviewer for #1472 · 54.4 AIC · ⌖ 5.75 AIC · ⊞ 6.3K ·
Comment /rust-review to run again

@jamesadevine

Copy link
Copy Markdown
Collaborator

@copilot Fix up PR description and implement feedback and suggestions.

… EMPTY_OVERRIDES static

Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com>

Copilot AI commented Jul 11, 2026

Copy link
Copy Markdown
Contributor Author

@copilot Fix up PR description and implement feedback and suggestions.

Done in commit 03505df. Two improvements from the rust review:

  1. SafeOutputsVariant.is_reviewed: Added is_reviewed: bool field (set in the reviewed() constructor) and replaced the fragile variant.base == "SafeOutputs_Reviewed" string comparison with variant.is_reviewed.

  2. Consolidated EMPTY_OVERRIDES: The two independent static EMPTY: OnceLock<HashMap<String, PoolConfig>> locals (one in PoolConfig::overrides(), one in FrontMatter::pool_overrides()) are now a single module-level static EMPTY_OVERRIDES.

Updated PR description (reflecting the pool.overrides nested key from the previous commit):


Summary

The compiler previously cloned the top-level pool: onto every generated job, forcing lightweight downstream jobs (Detection, SafeOutputs, Conclusion) onto scarce specialized capacity. Adds pool.overrides to right-size individual jobs independently.

pool:
  name: SpecializedLinuxPool
  overrides:
    detection:
      vmImage: ubuntu-22.04
    safe-outputs:
      vmImage: ubuntu-22.04
    conclusion:
      vmImage: ubuntu-22.04

Per-job overrides are nested under pool.overrides (requires the object form of pool:); the legacy scalar pool: MyPool form does not support overrides.

src/compile/types.rsoverrides: HashMap<String, PoolConfig> added to PoolConfigFull (serde: overrides); FrontMatter::pool_overrides() delegates to pool.overrides(). Duplicate OnceLock statics consolidated into a single module-level EMPTY_OVERRIDES.

src/compile/common.rsPerJobPools struct (one resolved Pool per canonical job) + resolve_pool_overrides_typed() which applies overrides, inherits safe-outputs-reviewed from safe-outputs, warns on unknown keys, hard-errors on manual-review.

src/compile/agentic_pipeline.rsStandaloneCtx.pool: Poolpools: PerJobPools; each of the 6 job builders reads its own field. SafeOutputsVariant gains an is_reviewed: bool field so the reviewed-variant pool selection no longer relies on a string comparison against the job base name.

src/compile/onees_ir.rs — compile-time error if pool.overrides is non-empty on target: 1es; the 1ES template owns pool selection.

Constraints: manual-review key always rejected (agentless, fixed pool: server). Unknown keys warn and are ignored. Each override is validated through the same resolve_pool_typed rules as the default pool (no name+vmImage, demands requires name).

Docs: Full reference section added to front-matter.md including key table and trust boundary note for self-hosted overrides; trust boundary callout added to safe-outputs.md.

Test plan

12 new tests: 9 unit tests in common.rs covering default inheritance, per-key overrides, safe-outputs-reviewed inheritance, manual-review rejection, invalid pool combos, and demands composition; 3 integration tests in agentic_pipeline.rs verifying pools flow through build_canonical_jobs to the emitted job objects. cargo test — all passed.

@jamesadevine

Copy link
Copy Markdown
Collaborator

/rust-review

@github-actions

github-actions Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Rust PR Reviewer completed successfully!

@github-actions

Copy link
Copy Markdown
Contributor

🔍 Rust PR Review

Summary: Looks good overall — clean design, solid test coverage, good error handling. A couple of things worth a second look.

Findings

🔒 Security Concerns

  • docs/front-matter.md:333 — The trust boundary note only calls out Detection, SafeOutputs, and Conclusion (SC_WRITE_TOKEN exposure). It omits agent and setup: overriding those keys to a different self-hosted pool grants that pool's admins access to the Agent's working tree, checked-out source code, and the read-only ADO token. The trust-boundary risk is lower (no write token) but real — worth adding a sentence like: "For agent and setup, the overridden pool's administrators are trusted with the checked-out source tree and the read-only build token."

⚠️ Suggestions

  • src/compile/common.rs:1327 — Rejecting "manualreview" and "manual_review" with a hard error is slightly inconsistent: those are not valid keys (they'd just trigger the unknown-key warning path), so a user who fat-fingered manualreview gets a confusing hard error instead of a helpful "did you mean manual-review? (also not allowed)" message. Consider keeping only the canonical "manual-review" in the bail! list and letting the others fall through to the unknown-key warning (or add a typo-suggestion in the error message).

  • src/compile/agentic_pipeline.rs test pool_overrides_all_downstream_override — The test asserts Agent, Detection, and SafeOutputs pools but never checks Conclusion, even though the YAML includes conclusion: vmImage: ubuntu-22.04. Trivial to add assert_eq!(pool_name(job_pool_by_id(&jobs, "Conclusion").unwrap()), "ubuntu-22.04").

✅ What Looks Good

  • safe-outputs-reviewed inheriting from safe-outputs (with independent override possible) is the right default — prevents a footgun where reviewed-variant gets wrong pool by accident.
  • The 1ES early-bail in onees_ir.rs is clean and gives a clear, actionable error message.
  • EMPTY_OVERRIDES: OnceLock is the right Rust idiom for a shared static empty-map reference.
  • SanitizeConfigTrait impl on PoolConfigFull correctly recurses into overrides.values_mut() so sanitization is not silently dropped on nested pool configs.
  • eprintln! for unknown-key warnings is consistent with the rest of common.rs.
  • Test coverage at both unit (common.rs) and integration (agentic_pipeline.rs) levels is thorough.

Generated by Rust PR Reviewer for #1472 · 47.4 AIC · ⌖ 5.84 AIC · ⊞ 6.3K ·
Comment /rust-review to run again

@jamesadevine

Copy link
Copy Markdown
Collaborator

@copilot implement suggestions

Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com>

Copilot AI commented Jul 11, 2026

Copy link
Copy Markdown
Contributor Author

@copilot implement suggestions

Done in the latest commit. Three changes from the second rust-review:

  1. manual-review rejection (common.rs): Only the canonical "manual-review" key is hard-rejected now. Typo variants like "manualreview" and "manual_review" fall through to the existing unknown-key warning path, giving users a more helpful message.

  2. Conclusion pool assertion (agentic_pipeline.rs): Added assert_eq!(pool_name(job_pool_by_id(&jobs, "Conclusion").unwrap()), "ubuntu-22.04") to pool_overrides_all_downstream_override — all 12 pool_overrides tests pass.

  3. Trust boundary note (docs/front-matter.md): Added a sentence covering agent and setup overrides: "For agent and setup, the overridden pool's administrators are trusted with the checked-out source tree and the read-only build token."

@jamesadevine

Copy link
Copy Markdown
Collaborator

@copilot why does the new override frontmatter support legacy strings? They are automatically transformed by the codemod before compilation. The new code should never support legacy string format.

…legacy strings)

Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com>

Copilot AI commented Jul 11, 2026

Copy link
Copy Markdown
Contributor Author

@copilot why does the new override frontmatter support legacy strings? They are automatically transformed by the codemod before compilation. The new code should never support legacy string format.

Fixed in 6f879b6. Changed PoolConfigFull::overrides from HashMap<String, PoolConfig> to HashMap<String, PoolConfigFull> — the legacy Name(String) variant is no longer accepted for override values. Updated EMPTY_OVERRIDES, PoolConfig::overrides(), FrontMatter::pool_overrides(), and resolve_pool_overrides_typed accordingly. Test override maps updated to use PoolConfigFull directly. All 2567 tests pass.

jamesadevine and others added 3 commits July 13, 2026 10:34
Remove the untagged PoolConfig::Name(String) variant and collapse PoolConfigFull into PoolConfig. Legacy scalar pool: sources are still accepted as input and migrated to object form by codemod 0002_pool_object_form before typed deserialization, so the typed layer is object-only.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: fc152016-8608-47c3-bb4b-30d3565e63f0
Production code in resolve_pool_typed read the pub fields directly, which left name()/vm_image() marked #[cfg(test)] and os() marked #[allow(dead_code)]. Use the accessors consistently so they are genuinely production-used and drop the test-only/dead-code attributes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: fc152016-8608-47c3-bb4b-30d3565e63f0
@jamesadevine jamesadevine merged commit f1109c6 into main Jul 13, 2026
9 checks passed
@jamesadevine jamesadevine deleted the copilot/better-front-matter-plan branch July 13, 2026 09:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants